package qy.qyalgotrader.utils.plotter;

import jep.JepException;
import jep.python.PyObject;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.OrderedMap;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.bar.Bars;
import qy.jalgotrade.dataseries.DataSeries;
import qy.jalgotrade.utils.CommonUtils;

import java.lang.reflect.InvocationTargetException;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * @author c-geo
 *
 */
public class RealtimeSubplot {

	private static final Logger logger = LoggerFactory.getLogger(RealtimeSubplot.class);

	/**
	 *
	 */
	public static List<String> colors = Arrays.asList("blue", "olive", "magenta", "darkcyan", "black", "green", "red",
	        "coral", "gray", "blueviolet", "orange", "cornflowerblue"); // XXX: qy-pyalgotrade:

	private final Bar.Frequency __frequency;

	private String __name;

	/**
	 * Series by name.
	 */
	private final OrderedMap<String, RealtimeSeries> __series;

	/**
	 * Maps a function to a Series.
	 */
	private final Map<Function<Bars, Double>, RealtimeSeries> __callbacks;

	private int __nextColor;

	/**
	 * 
	 */
	public RealtimeSubplot(Bar.Frequency frequency) {

		__frequency = frequency;
		__name = "";
		__series = MapUtils.orderedMap(new HashMap<>());
		__callbacks = new HashMap<>();
		__nextColor = 0;
	}

	private String __getColor(RealtimeSeries<?, ?> series) {

		String ret = series.getColor();
		if (ret == null || ret.length() == 0) {
			ret = colors.get(__nextColor++ % colors.size());
		}
		return ret;
	}

	public boolean isEmpty() {

		return __series.size() == 0;
	}

	public Bar.Frequency getFrequency() {

		return __frequency;
	}

	public String getName() {

		return __name;
	}

	public void setName(String name) {

		__name = name;
	}

	public Map<String, RealtimeSeries> getAllSeries() {

		return __series;
	}

	public RealtimeSeries findSeries(String name) {

		return __series.get(name);
	}

	public RealtimeSeries<Double, Double> getSeries(String name)
	        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

		return getSeries(name, RealtimeLineMarker.class);
	}

	public <E, T> RealtimeSeries<E, T> getSeries(String name, Class<? extends RealtimeSeries> defaultClass)
	        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

		RealtimeSeries ret = __series.get(name);
		if (ret == null) {
			ret = ConstructorUtils.invokeConstructor(defaultClass, __frequency, name);
			__series.put(name, ret);
		}
		return ret;
	}

	public RealtimeSeries<Double, Double> getCustomMarksSeries(String name) {

		try {
			return getSeries(name, RealtimeCustomMarker.class);
		} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException
		        | InstantiationException e) {
			logger.error("Exception occured creating CustomMarker: {}", ExceptionUtils.getStackTrace(e));
			return null;
		}
	}

	/**
	 * Add a DataSeries to the subplot.
	 * 
	 * @param label      A name for the DataSeries values.
	 * @param dataSeries The DataSeries to add.
	 * @throws InstantiationException InstantiationException
	 * @throws InvocationTargetException InvocationTargetException
	 * @throws IllegalAccessException IllegalAccessException
	 * @throws NoSuchMethodException NoSuchMethodException
	 */
	public void addDataSeries(String label, DataSeries dataSeries)
	        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

		addDataSeries(label, dataSeries, RealtimeLineMarker.class);
	}

	/**
	 * Add a DataSeries to the subplot.
	 * 
	 * @param label        A name for the DataSeries values.
	 * @param dataSeries   The DataSeries to add.
	 * @param defaultClass defaultClass
	 * @throws InstantiationException InstantiationException
	 * @throws InvocationTargetException InvocationTargetException
	 * @throws IllegalAccessException IllegalAccessException
	 * @throws NoSuchMethodException NoSuchMethodException
	 */
	public void addDataSeries(String label, DataSeries dataSeries, Class<? extends RealtimeSeries> defaultClass)
	        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

		Function<Bars, Double> callback = new Function<Bars, Double>() {

			@Override
			public Double apply(Bars bars) {

				try {
					return (Double) dataSeries.__getitem__(-1);
				} catch (IndexOutOfBoundsException e) {
					return Double.NaN;
				}
			}
		};
		__callbacks.put(callback, getSeries(label, defaultClass));
	}

	/**
	 * Add a callback that will be called on each bar.
	 * 
	 * @param label    A name for the series values.
	 * @param callback A function that receives a :class:`pyalgotrade.bar.Bars` instance as a parameter
	 *                 and returns a number or None.
	 */
	public void addCallback(String label, Function<Bars, Double> callback) {

		addCallback(label, callback, RealtimeLineMarker.class);
	}

	/**
	 * Add a callback that will be called on each bar.
	 * 
	 * @param label        A name for the series values.
	 * @param callback     A function that receives a :class:`pyalgotrade.bar.Bars` instance as a
	 *                     parameter and returns a number or None.
	 * @param defaultClass defaultClass
	 */
	public void addCallback(String label, Function<Bars, Double> callback, Class<? extends RealtimeSeries> defaultClass) {

		try {
			__callbacks.put(callback, getSeries(label, defaultClass));
		} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException
		        | InstantiationException e) {
			logger.error("Exception occured creating Series: {}", ExceptionUtils.getStackTrace(e));
		}
	}

	/**
	 * Add a horizontal line to the plot.
	 * 
	 * @param label A label.
	 * @param level The position for the line.
	 */
	public void addLine(String label, double level) {

		addCallback(label, bars -> {
			return level;
		});
	}

	/**
	 * 
	 * @param bars bars
	 */
	public void onBars(Bars bars) {

		ZonedDateTime dateTime = bars.getDateTime();
		for (Map.Entry<Function<Bars, Double>, RealtimeSeries> e : __callbacks.entrySet()) {
			Function<Bars, Double> cb = e.getKey();
			RealtimeSeries series = e.getValue();
			series.addValue(dateTime, cb.apply(bars));
		}
	}

	/**
	 * 
	 * @param mplSubplot mplSubplot
	 * @param dateTimes dateTimes
	 * @throws JepException JepException
	 */
	public void plot(PyObject mplSubplot, List<ZonedDateTime> dateTimes) throws JepException {

		for (RealtimeSeries series : __series.values()) {
			String color = "";
			if (series.needColor()) {
				color = __getColor(series);
			}
			series.plot(mplSubplot, dateTimes, color);
		}

		postPlotFun(mplSubplot);
	}

	/**
	 * 
	 * @param mplSubplot mplSubplot
	 * @throws JepException JepException
	 */
	protected void postPlotFun(PyObject mplSubplot) throws JepException {

		PyObject allLabels = CommonUtils.createPyList(RealtimeStrategyPlotter.jepInterpreter(),
		        CommonUtils.genPyObjectName(getClass().getName(), "postPlotFun", getName() + "_allLabels"),
				true);
		for (String key : getAllSeries().keySet()) {
			CommonUtils.pyInvoke(allLabels, "append", key);
		}
		// Legend:
		// mplSubplot.legend(labels=allLabels, loc="lower left", shadow=True, handlelength=0.5, handletextpad=0.3, labelspacing=0.2, framealpha=0.7)
		// @formatter:off
		PyObject myMplObjectGw = RealtimeStrategyPlotter.jepInterpreter().getValue("myMplObjectGw", PyObject.class);
		Object[] args = new Object[] { mplSubplot, "legend" };
		Map<String, Object> kwargs = MapUtils.putAll(new HashMap<>(), new Object[][] {
				{ "labels", allLabels },
				{ "loc", "lower left" },
				{ "shadow", true },
				{ "handlelength", 0.5 },
				{ "handletextpad", 0.3 },
				{ "labelspacing", 0.2 },
				{ "framealpha", 0.7 },
			});
		// @formatter:on
		CommonUtils.pyInvoke(myMplObjectGw, "invoke", args, kwargs);
		// Don't scale the Y axis:
		// mplSubplot.yaxis.set_major_formatter(ticker.ScalarFormatter(useOffset=False))
		PyObject arg = CommonUtils.pyInvokeAs(PyObject.class,
		        RealtimeStrategyPlotter.jepInterpreter().getValue("ticker", PyObject.class), "ScalarFormatter",
		        MapUtils.putAll(new HashMap<String, Object>(), new Object[][] { { "useOffset", false } })); // ticker.ScalarFormatter(useOffset=False)
		args = new Object[] { mplSubplot.getAttr("yaxis", PyObject.class), "set_major_formatter", arg };
		CommonUtils.pyInvoke(myMplObjectGw, "invoke", args);
		// 设置纵轴可视范围:
		double minVal = Double.NaN;
		double maxVal = Double.NaN;
		// TODO:
	}
}
